﻿using System;
using System.Data.Common;
using System.IO;
using System.Runtime.CompilerServices;

namespace SSAT.Utils
{
    /// <summary>
    /// Logs exceptions. SqlExceptions are placed into the text file SQLErrorLog.log, 
    /// and Application exceptions - Log.log. This class is thread-safe.
    /// </summary>
    public static class Logging
    {
        public const string SqlExceptionsLogFileName = "SQLErrorLog.log";
        public const string ProgramExceptionsLogFileName = "Log.log";
        public const string LogEntrySeparator = "-------------------";
        public const string SqlExceptionMessagePrefix = "[SqlException]";
        public const string ErrorInTheApplication = "[Error in the Application]";

        private static readonly object _syncRoot = new object();

        /// <summary>
        /// Handles exception and writes it into the file and application console. 
        /// SqlExceptions are placed into the file SQLErrorLog.log, and Application
        /// exceptions - Log.log.
        /// </summary>
        /// <param name="error">Exception to handle.</param>
        public static void Handle(Exception error)
        {
            HandleSafely(delegate(Exception ex)
            {
                if (ex is DbException)
                {
                    ProcessDbExceptionMessage(ex.Message);
                }
                else
                {
                    AppConsole.WriteError(string.Format(@"{0}: {1}", ErrorInTheApplication, ex.Message));
                    WriteApplicationException(ex.ToString());
                }
            }, error);
        }

        private static void ProcessDbExceptionMessage(string message)
        {
            if (string.IsNullOrEmpty(message))
            {
                return;
            }
            AppConsole.WriteError(string.Format(@"{0}: {1}", SqlExceptionMessagePrefix, message));
            WriteDbException(message);
        }

        private static void HandleSafely<T>(Action<T> handleMethod, T error)
            where T : class
        {
            if (error == null)
            {
                return;
            }
            try
            {
                handleMethod.Invoke(error);
            }
            catch (Exception ex)
            {
                NotifyUserAboutError(string.Format("The following error occured during handling exception:\n{0}", ex.Message));
            }
        }

        /// <summary>
        /// Extracts information from the CLS and not CLS compliant exceptions.
        /// </summary>
        /// <param name="error">Exception from which you need to extract error message.</param>
        /// <returns>If error is null it returns empty string. If error is an Exception derived type
        /// it returns it's Message, otherwise just string representation of passed object. If the
        /// error is RuntimeWrappedException it returns it's WrappedException.</returns>
        public static string GetExceptionMessage(object error)
        {
            string result = string.Empty;
            if (error != null)
            {
                if (error is RuntimeWrappedException)
                {
                    result = GetExceptionMessage((error as RuntimeWrappedException).WrappedException);
                }
                else if (error is Exception)
                {
                    result = (error as Exception).Message;
                }
                else
                {
                    result = error.ToString();
                }
            }
            return result;
        }

        private static void WriteEntry(string file, string message)
        {
            try
            {
                lock (_syncRoot)
                {
                    DateTime currentTime = DateTime.Now;
                    using (StreamWriter writer = new StreamWriter(file, true))
                    {
                        writer.WriteLine(LogEntrySeparator);
                        writer.WriteLine(string.Format("Date: {0}", currentTime.ToString()));

                        writer.WriteLine(message);

                        writer.WriteLine(LogEntrySeparator);
                    }
                }
            }
            catch (Exception ex)
            {
                NotifyUserAboutError(string.Format(@"Can't log message to file ""{0}"", because ""{1}"".", file, ex.Message));
            }
        }

        private static void NotifyUserAboutError(string message)
        {
            try
            {
                AppConsole.WriteError(message);
            }
            catch (Exception ex)
            {
                /* If program flow reached this lines then we can't write exception
                 * into the file and even console window. All we can do in this situation
                 * is to inform user about this exception via dialog and shutdown program 
                 * to prevent data damaging and inadequate application behaviour.
                 */
                try
                {
                    UIHelper.ShowErrorDialog(ex);
                }
                catch
                {
                    /* We can't even show error dialog. Then we just
                     * silently swallow this exception.
                     */
                }
                finally
                {
                    Environment.Exit(0);
                }
            }
        }

        private static void WriteDbException(string error)
        {
            WriteEntry(SqlExceptionsLogFileName, error);
        }

        private static void WriteApplicationException(string error)
        {
            WriteEntry(ProgramExceptionsLogFileName, error);
        }
    }
}
